/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.web30.feed;

import com.sun.syndication.feed.WireFeed;
import com.sun.syndication.feed.rss.Channel;
import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.feed.synd.SyndPerson;
import com.sun.syndication.feed.synd.SyndPersonImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.WireFeedOutput;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.james.mime4j.message.Message;
import org.apache.james.mime4j.field.address.Mailbox;
import org.apache.james.mime4j.message.BinaryBody;
import org.apache.james.mime4j.message.Body;
import org.apache.james.mime4j.message.BodyPart;
import org.apache.james.mime4j.message.Multipart;
import org.apache.james.mime4j.message.TextBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.james.mime4j.field.MailboxListField;
import org.apache.james.mime4j.parser.Field;

/**
 *
 * @author xiazy
 */
public class FeedBuilder {

    /**
     * Root url for this feed
     */
    private URI _rootUri = null;
    /**
     * The base url for entry of feed
     */
    private URI _entryBaseUri = null;
    /**
     * The comments base url
     */
    private URI _commentsBaseUri = null;
    /**
     * The map for finding entry which is not comments of others
     */
    private HashMap<String, Object> _mainNews = new HashMap<String, Object>();
    /**
     * The returned feed type
     */
    private String type = RSS;
    /**
     * When this flag is ture, the feed will contains comments of another entries
     */
    private boolean _containComments = false;
    public static final String ATOM = "atom_1.0";
    public static final String RSS = "rss_2.0";
    public static final String WEB30_URL = "www.web30core.org";

    public FeedBuilder(URI rootUri,
            URI entryBaseUri,
            URI commentsBaseUri,
            boolean containComments,
            String type) {
        this._rootUri = rootUri;
        this._entryBaseUri = entryBaseUri;
        this._commentsBaseUri = commentsBaseUri;
        this._containComments = containComments;
    }

    public static String feed2String(WireFeed feed) throws FeedException {
        WireFeedOutput out = new WireFeedOutput();
        return out.outputString(feed);
    }

    public SyndFeed mime2Feed(ArrayList<Message> articles) throws FeedException {
        SyndFeed feed = new SyndFeedImpl();
        if (this.type.compareToIgnoreCase(RSS) == 0) {
            feed.setFeedType(RSS);
        } else {
            feed.setFeedType(ATOM);
        }
        feed.setLink(this._rootUri.toASCIIString());
        feed.setTitle(WEB30_URL);
        feed.setDescription(WEB30_URL);
        List entries = new ArrayList();
        for (Message article : articles) {
            SyndEntry temp = mime2Entry(article);
            if (temp != null) {
                entries.add(temp);
            }
        }
        feed.setEntries(entries);
        return feed;
    }

    private String getInReplyTo(Field inReplyTo) {
        if (inReplyTo == null) {
            return null;
        }
        return inReplyTo.getBody();
    }

    private void addArticleAsComment(Object entry,
            String parentId) {
        //If type is RSS
        if (this.type.compareToIgnoreCase(RSS) == 0) {
            Item temp = (Item) this._mainNews.get(parentId);
            //Has parent entry
            if (temp != null) {
                if (temp.getComments() == null) {
                    String mid = parentId.substring(parentId.indexOf("<") + 1,
                            parentId.lastIndexOf(">"));
                    temp.setComments(this._commentsBaseUri + mid);
                }
            }
        }
        //TODO: type is ATOM Feed
    }

    private SyndEntry mime2Entry(Message article) {
        SyndEntry entry = new SyndEntryImpl();
        entry.setPublishedDate(article.getDate());
        entry.setTitle(article.getSubject());
        entry.setLink(this._entryBaseUri + article.getMessageId().substring(1, article.getMessageId().length() - 1));
        List authors = new ArrayList();
        Field from = article.getHeader().getField("From");
        if (!(from instanceof MailboxListField)) {
            return null;
        }
        for (Mailbox box : article.getFrom()) {
            SyndPerson author = new SyndPersonImpl();
            if (box.getAddress() == null) {
                continue;
            }
            author.setEmail(box.getAddress());
            String temp = box.getName();
            if (temp == null) {
                temp = box.getLocalPart();
            }
            author.setName(temp);
            authors.add(author);
        }
        entry.setAuthors(authors);
        List contents = new ArrayList();
        //Multipart multipart = (Multipart) article.getBody();
        if (article.isMultipart()) {
            contents.add(createMultipartsContent((Multipart) article.getBody(),
                    article.getMessageId().substring(1, article.getMessageId().length() - 1)));
        } else {
            String file = article.getFilename();
            if (file == null) {
                file = article.getMessageId().substring(1, article.getMessageId().length() - 1);
            } else {
                file = article.getMessageId() + "_" + file;
            }
            String mimeType = article.getMimeType();
            if (mimeType == null) {
                mimeType = "text/plain";
            }
            String charset = article.getCharset();
            if (charset == null) {
                charset = "UTF-8";
            }
            contents.add(createSingleBodyContent(article.getBody(), mimeType, file, charset));
        }
        entry.setContents(contents);
        return entry;
    }

    private Item mime2Item(Message article) {
        SyndEntry entry = this.mime2Entry(article);
        if (entry == null) {
            return null;
        }
        RSSConvert conv = new RSSConvert();
        Item item = conv.convertRSSItem(entry);
        String id = this.getInReplyTo(article.getHeader().getField("In-Reply-To"));
        // This is comment entry
        this._mainNews.put(article.getMessageId(), item);
        if (id != null) {
            //Ignore comments entries
            this.addArticleAsComment(item, id);
            if (this._containComments == false) {
                return null;
            }
        }
        return item;
    }

    public Channel mime2Rss(ArrayList<Message> articles) {
        Channel feed = new Channel();
        feed.setFeedType(RSS);
        feed.setLink(this._rootUri.toASCIIString());
        feed.setTitle(WEB30_URL);
        feed.setDescription(WEB30_URL);
        List items = new ArrayList(articles.size());
        for (Message article : articles) {
            Item temp = this.mime2Item(article);
            if (temp != null) {
                items.add(temp);
            }
        }
        feed.setItems(items);
        return feed;
    }

    /**
     * Create feed content for multi-part mime message
     * @param parts
     * @param relativePath using message id as prefix of name of attachment files
     * @return
     */
    private SyndContent createMultipartsContent(Multipart parts, String relativePath) {
        SyndContent description = new SyndContentImpl();
        description.setType("text/html");
        StringBuffer buf = new StringBuffer();
        for (BodyPart part : parts.getBodyParts()) {
            if (part.isMultipart()) {
                buf.append(this.getContent(part, relativePath));
            } else {
                String mimeType = part.getMimeType();
                if (mimeType == null) {
                    mimeType = "text/plain";
                }
                if (part.getBody() instanceof TextBody) {
                    String charset = part.getCharset();
                    if (charset == null) {
                        charset = "UTF-8";
                    }
                    buf.append(this.getTextContent((TextBody) (part.getBody()),
                            mimeType,
                            charset));
                } else if (part.getBody() instanceof BinaryBody) {
                    String fileName = part.getFilename();
                    if (fileName == null) {
                        fileName = relativePath;
                    } else {
                        fileName = relativePath + "_" + fileName;
                    }
                    buf.append(this.getBinaryContent((BinaryBody) (part.getBody()), fileName, mimeType));
                } else {
                    buf.append("<p>unsupported body</p>");
                }
            }
        }
        description.setValue(buf.toString());
        return description;
    }

    /**
     * Create feed content for single body of mime message
     * @param body body of mime message
     * @param type mime type, such as text/plain
     * @param fileName the prefix of attachment files
     * @param charset charset of mime message
     * @return
     */
    private SyndContent createSingleBodyContent(Body body,
            String type,
            String prefix,
            String charset) {
        if (body == null) {
            return null;
        }
        SyndContent description = new SyndContentImpl();
        StringBuffer buf = new StringBuffer();
        description.setType("text/html");
        if (body instanceof TextBody) {
            buf.append(this.getTextContent((TextBody) body, type, charset));
        } else if (body instanceof BinaryBody) {
            buf.append(this.getBinaryContent((BinaryBody) body, prefix, type));
        } else {
            buf.append("<p>empty message</p>");
        }
        description.setValue(buf.toString());
        return description;
    }

    private boolean isImage(String mimeType) {
        if (mimeType == null) {
            return false;
        }
        if (mimeType.indexOf("image") != -1) {
            return true;
        } else {
            return false;
        }
    }

    private boolean isTextPlain(String mimeType) {
        if (mimeType == null) {
            return false;
        }
        if (mimeType.indexOf("text/plain") != -1) {
            return true;
        } else {
            return false;
        }
    }

    private boolean isHTML(String mimeType) {
        if (mimeType == null) {
            return false;
        }
        if (mimeType.indexOf("text/html") != -1) {
            return true;
        } else {
            return false;
        }
    }

    private StringBuffer getContent(BodyPart part, String relativePath) {
        StringBuffer buf = new StringBuffer();
        if (part.isMultipart()) {
            buf.append(this.getContent(part, relativePath).toString());
        } else {
            String mimeType = part.getMimeType();
            if (mimeType == null) {
                mimeType = "text/plain";
            }
            Body body = part.getBody();
            if (body instanceof TextBody) {
                String charset = part.getCharset();
                buf.append(this.getTextContent((TextBody) body, mimeType, charset));
            } else if (body instanceof BinaryBody) {
                String fileName = part.getFilename();
                buf.append(this.getBinaryContent((BinaryBody) body, fileName, mimeType));
            } else {
                buf.append("<p>empty message</p>");
            }
        }
        return buf;
    }

    private String getBinaryContent(Body body, String fileName, String mimeType) {
        BinaryBody binary = (BinaryBody) body;
        try {
            binary.writeTo(new FileOutputStream(fileName));
        } catch (IOException ex) {
            Logger.getLogger(FeedBuilder.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        if (this.isImage(mimeType)) {
            return "<p><img src=" + fileName + " alt=\"\" /></p>";
        }
        return "<p><a href\"" + this._rootUri + "/cache/" + fileName + "\"</p>";
    }

    private String getTextContent(TextBody text, String mimeType, String charset) {
        try {
            if (this.isTextPlain(mimeType)) {
                String line = null;
                BufferedReader reader = new BufferedReader(text.getReader());
                StringWriter cache = new StringWriter(2048);
                BufferedWriter writer = new BufferedWriter(cache);
                writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\"><p>");
                while ((line = reader.readLine()) != null) {
                    writer.write(line);
                    writer.write("<br/>");
                }
                writer.write("</p></html>");
                writer.close();
                return cache.getBuffer().toString();
            } else if (this.isHTML(mimeType)) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                text.writeTo(out);
                return out.toString(charset);
            } else {
                return "<p>unsupported body</p>";
            }
        } catch (IOException ex) {
            Logger.getLogger(FeedBuilder.class.getName()).log(Level.SEVERE, null, ex);
            return new String("Error in reading");
        }
    }
}
